using System;
using System.IO;
using System.Runtime.InteropServices;


namespace Ultima {
  public class TileMatrixPatch {
    private readonly int m_LandBlocks;
    private readonly int m_StaticBlocks;

    public TileMatrixPatch( TileMatrix matrix, int index ) {
      string mapDataPath = Client.GetFilePath( "mapdiff{0}.mul", index );
      string mapIndexPath = Client.GetFilePath( "mapdiffl{0}.mul", index );

      if ( mapDataPath != null && mapIndexPath != null )
        this.m_LandBlocks = PatchLand( matrix, mapDataPath, mapIndexPath );

      string staDataPath = Client.GetFilePath( "stadiff{0}.mul", index );
      string staIndexPath = Client.GetFilePath( "stadiffl{0}.mul", index );
      string staLookupPath = Client.GetFilePath( "stadiffi{0}.mul", index );

      if ( staDataPath != null && staIndexPath != null && staLookupPath != null )
        this.m_StaticBlocks = PatchStatics( matrix, staDataPath, staIndexPath, staLookupPath );
    }

    public int LandBlocks {
      get { return this.m_LandBlocks; }
    }

    public int StaticBlocks {
      get { return this.m_StaticBlocks; }
    }

    [DllImport( "Kernel32" )]
    private static extern unsafe int _lread( IntPtr hFile, void* lpBuffer, int wBytes );

    private unsafe int PatchLand( TileMatrix matrix, string dataPath, string indexPath ) {
      using ( var fsData = new FileStream( dataPath, FileMode.Open, FileAccess.Read, FileShare.Read ) ) {
        using ( var fsIndex = new FileStream( indexPath, FileMode.Open, FileAccess.Read, FileShare.Read ) ) {
          var indexReader = new BinaryReader( fsIndex );

          var count = (int)( indexReader.BaseStream.Length / 4 );

          for ( int i = 0; i < count; ++i ) {
            int blockID = indexReader.ReadInt32();
            int x = blockID / matrix.BlockHeight;
            int y = blockID % matrix.BlockHeight;

            fsData.Seek( 4, SeekOrigin.Current );

            var tiles = new Tile[64];

            fixed ( Tile* pTiles = tiles ) {
              _lread( fsData.Handle, pTiles, 192 );
            }

            matrix.SetLandBlock( x, y, tiles );
          }

          return count;
        }
      }
    }

    private unsafe int PatchStatics( TileMatrix matrix, string dataPath, string indexPath, string lookupPath ) {
      using ( var fsData = new FileStream( dataPath, FileMode.Open, FileAccess.Read, FileShare.Read ) ) {
        using ( var fsIndex = new FileStream( indexPath, FileMode.Open, FileAccess.Read, FileShare.Read ) ) {
          using ( var fsLookup = new FileStream( lookupPath, FileMode.Open, FileAccess.Read, FileShare.Read ) ) {
            var indexReader = new BinaryReader( fsIndex );
            var lookupReader = new BinaryReader( fsLookup );

            var count = (int)( indexReader.BaseStream.Length / 4 );

            var lists = new HuedTileList[8][];

            for ( int x = 0; x < 8; ++x ) {
              lists[x] = new HuedTileList[8];

              for ( int y = 0; y < 8; ++y )
                lists[x][y] = new HuedTileList();
            }

            for ( int i = 0; i < count; ++i ) {
              int blockID = indexReader.ReadInt32();
              int blockX = blockID / matrix.BlockHeight;
              int blockY = blockID % matrix.BlockHeight;

              int offset = lookupReader.ReadInt32();
              int length = lookupReader.ReadInt32();
              lookupReader.ReadInt32(); // Extra

              if ( offset < 0 || length <= 0 ) {
                matrix.SetStaticBlock( blockX, blockY, matrix.EmptyStaticBlock );
                continue;
              }

              fsData.Seek( offset, SeekOrigin.Begin );

              int tileCount = length / 7;

              var staTiles = new StaticTile[tileCount];

              fixed ( StaticTile* pTiles = staTiles ) {
                _lread( fsData.Handle, pTiles, length );

                StaticTile* pCur = pTiles, pEnd = pTiles + tileCount;

                while ( pCur < pEnd ) {
                  lists[pCur->m_X & 0x7][pCur->m_Y & 0x7].Add( (short)( ( pCur->m_ID & 0x3FFF ) + 0x4000 ), pCur->m_Hue,
                                                              pCur->m_Z );
                  ++pCur;
                }

                var tiles = new HuedTile[8][][];

                for ( int x = 0; x < 8; ++x ) {
                  tiles[x] = new HuedTile[8][];

                  for ( int y = 0; y < 8; ++y )
                    tiles[x][y] = lists[x][y].ToArray();
                }

                matrix.SetStaticBlock( blockX, blockY, tiles );
              }
            }

            return count;
          }
        }
      }
    }
  }
}